'use strict'; const { Document } = require('/document.js'); const { DocumentCommand, CompoundCommandBuilder } = require('/commands.js'); const { Dialog, DialogResult, UnitType } = require('/dialog.js'); const { CurveBuilder, PolyCurve } = require('/geometry.js'); function isOk(result) { return (result?.value ?? result) === DialogResult.Ok.value; } function subdividePolyCurve(polyCurve, nodesPerSegment) { const newPolyCurve = PolyCurve.create(); for (const curve of polyCurve) { const firstPoint = curve.beziers.first?.start; if (!firstPoint) continue; const builder = CurveBuilder.create(); builder.begin(firstPoint); for (const bez of curve.beziers) { if (nodesPerSegment <= 1) { builder.addBezierXY(bez.c1.x, bez.c1.y, bez.c2.x, bez.c2.y, bez.end.x, bez.end.y); } else { let remaining = bez; const n = nodesPerSegment; for (let i = 0; i < n - 1; i++) { const t = 1 / (n - i); const paramT = remaining.getParamAtLength(remaining.length * t); const pair = remaining.split(paramT); builder.addBezierXY(pair.left.c1.x, pair.left.c1.y, pair.left.c2.x, pair.left.c2.y, pair.left.end.x, pair.left.end.y); remaining = pair.right; } builder.addBezierXY(remaining.c1.x, remaining.c1.y, remaining.c2.x, remaining.c2.y, remaining.end.x, remaining.end.y); } } if (curve.isClosed) builder.close(); newPolyCurve.addCurve(builder.createCurve()); } return newPolyCurve; } function jitterPolyCurve(polyCurve, maxDx, maxDy, mode, seed) { let s = seed >>> 0; function rand() { s = (Math.imul(s, 1664525) + 1013904223) >>> 0; return s / 0x100000000; } function randSigned() { return rand() * 2 - 1; } const newPolyCurve = PolyCurve.create(); for (const curve of polyCurve) { const firstPoint = curve.beziers.first?.start; if (!firstPoint) continue; let bezArray = []; for (const bez of curve.beziers) bezArray.push(bez); function shouldJitter(idx) { switch (mode) { case 0: return true; // All Nodes case 1: return rand() > 0.5; // Random Nodes case 2: return idx % 2 === 0; // Skip Even case 3: return idx % 2 !== 0; // Skip Odd default: return true; } } const builder = CurveBuilder.create(); const doStart = shouldJitter(0); builder.begin({ x: firstPoint.x + (doStart ? randSigned() * maxDx : 0), y: firstPoint.y + (doStart ? randSigned() * maxDy : 0) }); for (let i = 0; i < bezArray.length; i++) { const bez = bezArray[i]; const doJ = shouldJitter(i + 1); const dx = doJ ? randSigned() * maxDx : 0; const dy = doJ ? randSigned() * maxDy : 0; builder.addBezierXY( bez.c1.x + dx, bez.c1.y + dy, bez.c2.x + dx, bez.c2.y + dy, bez.end.x + dx, bez.end.y + dy ); } if (curve.isClosed) builder.close(); newPolyCurve.addCurve(builder.createCurve()); } return newPolyCurve; } function applyToNodes(doc, selectedNodes, originals, nodesPerSeg, jx, jy, mode, seed) { const spread = doc.currentSpread; doc.executeCommand(DocumentCommand.createSetCurrentSpread(spread)); const cmds = []; for (let i = 0; i < selectedNodes.length; i++) { let poly = subdividePolyCurve(originals[i], nodesPerSeg); poly = jitterPolyCurve(poly, jx, jy, mode, seed); cmds.push(DocumentCommand.createSetCurves(selectedNodes[i].curvesInterface, poly)); } if (cmds.length === 1) { doc.executeCommand(cmds[0]); } else { const compound = CompoundCommandBuilder.create(); for (const cmd of cmds) compound.addCommand(cmd); doc.executeCommand(compound.createCommand()); } } function restoreOriginals(doc, selectedNodes, originals) { const spread = doc.currentSpread; doc.executeCommand(DocumentCommand.createSetCurrentSpread(spread)); const cmds = []; for (let i = 0; i < selectedNodes.length; i++) { cmds.push(DocumentCommand.createSetCurves(selectedNodes[i].curvesInterface, originals[i])); } if (cmds.length === 1) { doc.executeCommand(cmds[0]); } else { const compound = CompoundCommandBuilder.create(); for (const cmd of cmds) compound.addCommand(cmd); doc.executeCommand(compound.createCommand()); } } // ---- MAIN ---- const doc = Document.current; if (!doc) { console.log("ERROR: No document open."); } else { const selectedNodes = doc.selection.nodes.filter(n => n.isPolyCurveNode).toArray(); if (selectedNodes.length === 0) { console.log("Please select one or more curve nodes first."); } else { const originals = selectedNodes.map(n => n.polyCurve.clone()); let currentSeed = Math.floor(Math.random() * 0xffffffff); const dlg = Dialog.create("Curve Node Distributor"); const col = dlg.addColumn(); const g1 = col.addGroup(""); const nodesCtrl = g1.addUnitValueEditor("Nodes Per segment", UnitType.Number, UnitType.Number, 3, 1, 50).setPrecision(0); //const g2 = col.addGroup("Max Jitter X (px)"); const jitterXCtrl = g1.addUnitValueEditor("Jitter x", UnitType.Number, UnitType.Number, 20, 0, 5000).setPrecision(1); //const g3 = col.addGroup("Max Jitter Y (px)"); const jitterYCtrl = g1.addUnitValueEditor("Jitter y", UnitType.Number, UnitType.Number, 20, 0, 5000).setPrecision(1); //const g4 = col.addGroup("Node Selection Mode"); const modeCtrl = g1.addComboBox("", ["All Nodes", "Random Nodes", "Skip Even Nodes", "Skip Odd Nodes"], 0); // const g5 = col.addGroup(""); const previewBtn = g1.addButton("Shuffle"); previewBtn.onClickHandler = () => { currentSeed = Math.floor(Math.random() * 0xffffffff); applyToNodes(doc, selectedNodes, originals, Math.max(1, Math.round(nodesCtrl.value)), jitterXCtrl.value, jitterYCtrl.value, modeCtrl.selectedIndex, currentSeed); }; dlg.initialWidth = 400; const result = dlg.runModal(); if (isOk(result)) { applyToNodes(doc, selectedNodes, originals, Math.max(1, Math.round(nodesCtrl.value)), jitterXCtrl.value, jitterYCtrl.value, modeCtrl.selectedIndex, currentSeed); console.log(`Done! Processed ${selectedNodes.length} curve(s).`); } else { restoreOriginals(doc, selectedNodes, originals); console.log("Cancelled — original curves restored."); } } }